""" Defines all classes and functions related to images. """

try:
    import json
except ImportError:
    try:
        import simplejson as json
    except ImportError:
        pass

import pygame
from pygame import Surface, Rect

from sbak import (
    debug,
    error,
    resman
)


__all__ = (
    'Image',
    'from_json',
    'load_surface',
    'load',
)

class Image(resman.Resource):
    """ Image resource.
    
        Images can be drawn to the screen. They can have offsets which
        are added to the x and y coordinates when the image is drawn,
        and they can have default positions which are used to determine
        where to draw the image when no position is otherwise
        specified.
        
        Copying Images:
        
        When an image is copied using the Image.copy() method, the copy
        will have a `base` attribute that refers to the original image.
        Calling Image.revert() on that image copy will cause the copy's
        attribute to be replaced by the bases.
        
        To make a copied image unique, call Image.orphan() on it. This
        merely sets `base` to None, but calling the method instead of
        explicitly setting base to None is more meaningful and makes
        it more obvious what you're doing.
        
        copy(), revert(), and orphan() are properly implemented for all
        built-in subclasses of Image as well.
    """
    
    tag = 'image'
    
    def __init__(self, surface=None,
                       offset=(0,0),
                       pos=(0,0),
                       delay=1
                ):
        """ Initializes an Image.
        
            surface:  Surface object on which this Image will be based.
            offset:   Offset used when drawing.
            pos:      Default drawing position.
            delay:    Number of ticks to display the image in animations.
                      Must be integer value >= 1.
        """ 
        # Get the coordinates for default position and offset
        self._pos = map(int,pos[:2]) if pos is not None else [0,0]
        self._offset = map(int,offset[:2]) if offset is not None else [0,0]
        self._surf = None
        if surface is not None:
            self.surface = surface
        delay = int(delay)
        if delay < 1:
            raise ValueError("delay must be >= 1.")
        self._delay = int(delay)
        self.base = None
    
    @classmethod
    def from_json(cls, obj, resmgr=None):
        """ Create an Image from a JSON representation.
        
            obj:    The JSON representation. Must be a mapping
                    consistent with the form returned by to_json.
                    
            resmgr: A resource manager to use to load the Image
                    filename (if given) and to load surfaces. If
                    None, the global resource manager is used.
                    
            Returns an Image object.
        """
        if resmgr is None:
            resmgr = resman.get_mgr()
        
        d = dict(obj)
        
        if 'tag' in d:
            del d['tag']
        
        if 'surface' in d and isinstance(d['surface'], basestring):
            d['surface'] = resmgr.get(d['surface'], 'surface')
    
        img = cls(**d)
        
        return img
    
    def to_json(self, resmgr=None, usenone=False):
        """ Returns a JSON representation of the image.
        
            resmgr:     A resource manager to use to get the filename
                        of the image's surface's filename, if
                        available.
            
            usenone:    If true, the image's surface will be represented
                        as null (Python None) when it has no filename.
                        Otherwise, an exception will be raised when
                        encountering a surface that has no filename.
            
            ~~~ JSON Format Overview ~~~
            
            {
                'tag' : 'image',
                'surface' : <filename or null>,
                'offset' : [<int>, <int>],
                'pos' : [<int>, <int>],
                'delay' : <int>
            }
            
            ~~~ JSON Format Description ~~~
            
            'tag' :     The tag for an Image object is always 'image'.
            
            'surface' : The filename of the surface from which the
                        image was created or null if no filename is
                        available.
                
            'offset' :  The image's drawing offset.
            
            'pos' :     The image's default drawing position.
            
            'delay' :   The image's delay (for use in animations).
                        Must be at least 1.
        """
        
        if resmgr is None:
            resmgr = resman.get_mgr()
        
        d = {
            'tag' : self.tag,
            'surface' : resmgr.res_to_json(self.surface, usenone, True),
            'offset' : [int(n) for n in self.offset],
            'pos' : [int(n) for n in self.pos],
            'delay' : int(self.delay),
        }
        
        return d
    
    def draw(self,dest,offset=None,region=None):
        """ Draws the image to the given Pygame Surface.
            
            dest:   Destination surface onto which the Image will be
                    drawn.
                    
            offset: Optional drawing offset. If not None, this is added
                    to the internal offset and the result is the
                    position on the surface at which the image is
                    drawn. If None, the internal default position is
                    added to the internal offset and the sum becomes
                    the drawing posiiton.
            region: Region of internal surface to draw. Default to the
                    whole surface.
                    
            If self.surface is None, nothing will be drawn.
        """
        if self.surface is None:
            return
        if offset is None:
            offset = self._pos
        offset = map(sum, zip(offset, self._offset))
        if region:
            dest.blit(self.surface, offset, region)
        else:
            dest.blit(self.surface, offset)
    
    def update(self, e=None):
        " Does nothing; Exists for compatibility with Animations and Composites. "
        pass
    
    def get_rect(self,**kwargs):
        """ Returns the rectangular area of the surface.
            
            get_rect(...)
            get_region(...)
            
            This is the same as self.surface.get_rect() except that the
            rectangle is translated by the offset of the Image.
            
            If there is no surface a "Null" rectangle is returned, i.e.
            a rectangle positioned at 0,0 with size 0,0.
            
            This method is provided mostly for extra compatibility
            between surfaces and images.
        """
        return (self._surf.get_rect(**kwargs).move(self._offset)
                if self.surf is not None else Rect(0,0,0,0))
    
    get_region = get_rect
    
    def get_size(self):
        """ Returns size of the pygame.Surface object use to draw the image.
            If there is no surface (0,0) is returned.
        """
        return self.surf.get_size() if self.surf else (0,0)
    
    def copy(self):
        " Return a new Image with the same values. "
        img = Image(surf=self.surface, offset=self.offset, pos=self.pos,
                    delay=self.delay)
        img.base = self
        return img
    
    def revert(self):
        " Copy values from the base to the copy. "
        newSurface = base.surface
        newOffset = base.offset
        newPos = base.pos
        newDelay = base.delay
        
        newOffset = map(int, newOffset)
        newPos = map(int, newPos)
        newDelay = int(newDelay)
        if newDelay < 1:
            raise ValueError("base's delay is < 1.")
        
        self._surf = newSurface
        self._offset = newOffset
        self._pos = newPos
        self._delay = newDelay
    
    def orphan(self):
        ' Makes the Image "unique" by dropping the base reference. '
        self.base = None
    
    # Properties
    
    def set_surface(self,surf):
        if isinstance(surf,basestring):
            surf = resman.get(surf,'surface')
        self._surf = surf
    def get_surface(self):
        return self._surf
    surface = property(get_surface, set_surface, None,
        """ pygame.Surface object used to draw this image.
        
            This may be set to a string, in which case value is assumed
            to be a filename and the surface is acquired from the
            global resource manager.
        
            This may also be set to None, in which case the image will
            not be drawn, but doing so will cause many properties of
            the image which rely on the underlying surface to raise
            exceptions.
        """)
    
    surf = surface # For backwards compatibility
    
    def get_bpp(self):
        return self.surf.get_bitsize()
    bpp = property(get_bpp, doc =
        """ Number of bits per pixel used by the the image's Surface. """)
    
    def get_pos(self):
        return self._pos
    def set_pos(self,pos):
        self._pos[0],self._pos[1] = int(pos[0]),int(pos[1])
    pos = property(get_pos, set_pos, doc =
        """ Default drawing position as list of 2 integer coordinates. """)
    
    def get_x(self):
        return self._pos[0]
    def set_x(self,x):
        self._pos[0] = int(x)
    x = property(get_x, set_x, None,
                 """ X coordinate of the default drawing position. """)
    
    def get_y(self):
        return self._pos[1]
    def set_y(self,y):
        self._pos[1] = int(y)
    y = property(get_y, set_y, None,
                 """ Y coordinate of the default drawing position. """)
    
    def get_offset(self):
        return self._offset
    def set_offset(self,offset):
        self._offset[0],self._offset[1] = int(offset[0]),int(offset[1])
    offset = property(get_offset, set_offset, None,
                   """ Drawing offset as a list of 2 integer coordinates. """)
    
    def get_offset_x(self):
        return self._offset[0]
    def set_offset_x(self,x):
        self._offset[0] = int(x)
    offset_x = property(get_offset_x, set_offset_x, None,
                 """ X coordinate of the drawing offset. """)
    
    def get_offset_y(self):
        return self._offset[1]
    def set_offset_y(self,y):
        self._offset[1] = int(y)
    offset_y = property(get_offset_y, set_offset_y, None,
                 """ Y coordinate of the drawing offset. """)

    def set_delay(self,d):
        d = int(d)
        if d < 1:
            raise ValueError("Delay must be >= 1.")
        self._delay = d
    delay = property((lambda self: self._delay), set_delay, doc=
        """ Number of ticks to display the image in animations.
            Must be an integer value >= 1.
        """)

class Composite(Image):
    """ An image composed of one or more sub-images (layers).
    
        Each image associated with a composite is called a "layer".
        When a composite is drawn, each layer in the composite is
        drawn with the composite's offset.
    
        Composites can be used anywhere that images can be used.
        
        Composites can be treated as sequences. Each item is one of the
        layers, i.e. an Image or Image-compatible object.
        
        Supported methods include __setitem__, __getitem__, __iter__,
        and append.
        
        Composites make no use of the `surface` property of the Image
        class since their appearance is taken from their layers.
    """
    
    tag = 'composite'
    
    def __init__(self,  images=(),
                        offset=(0,0),
                        pos=(0,0),
                        delay=1 ):
        """ Initializes a new composite.
        
            images: Pass a sequence of Image or Image-compatible
                    objects. If None, the composite will not contain
                    any layers.
            offset: An offset to apply when drawing each layer.
            pos:    Default position at which the Composite should be
                    drawn when no position is given.
            delay:  Number of ticks to display the Composite when used
                    in an animation. Must be an integer value >= 1.
        """
        super(Composite,self).__init__(None,offset,pos,delay)
        self._image = []
        self.extend(images)
    
    @classmethod
    def from_json(cls, obj, resmgr=None):
        """ Creates a Composite from a JSON representation.
        
            obj:    The JSON representation of the composite to create.
            resmgr: A resource manager to use to load frames.
            
            Returns a new Composite object.
            
            NOTE: This class method is also used as the JSON decoder
            for the Animation class because the decoding procedure is
            identical. The only difference is that an Animation object
            is returned rather than a Composite.
        """
        if resmgr is None:
            resmgr = resman.get_mgr()
        
        # Create a copy of the given dict for editing
        d = dict(obj)
        
        # Remove the tag; the Composite constructor won't know what to do
        # with it.
        del d['tag']
           
        # If the composite has layers,
        if ('images' in d and d['images'] is not None):
            
            # Convert all JSON items to images.
            images = []
            for i in d['images']:
                
                # If the item is a string,
                if isinstance(i,basestring):
                    
                    # Load the file with that name.
                    images.append(resmgr.get(i, 'image'))
                
                # If it is not a string, assume it is another JSON object.
                else:
                    images.append(from_json(i, resmgr))
        
            # Replace the copy dict's JSONified images with real images.
            d['images'] = images
        
        # Create the composite.
        img = cls(**d)
        
        return img
    
    def to_json(self, resmgr=None, usenone=False):
        """ Constructs a JSON representation object for the composite.
        
            resmgr:     A resource manager to use to find file names of
                        contained images. If None is given, the global
                        resource manager is used.
            
            Returns a dict.
        """
        if resmgr is None:
            resmgr = resman.get_mgr()
        
        d = {
            "tag" : self.tag,
            "images" : [resmgr.res_to_json(img, usenone, True)
                        for img in self],
            "pos" : list(self.pos),
            "offset" : list(self.offset),
            "delay" : int(self.delay)
        }
        
        return d
    
    def draw(self, dest=None, offset=None, region=None):
        """ Draws all images in the composite.
        
            Images are drawn relative to the offset of the Composite.
            
            Images that are completely outside the area given are not
            drawn.
            
            FIXME: Area might not work correctly.
        """
        if offset is None:
            offset = [a+b for a,b in zip(self._pos, self._offset)]
        else:
            offset = [a+b for a,b in zip(self._pos, offset)]
        for img in self._image:
            img.draw(dest, offset, region)
    
    def update(self):
        """ Call update() on all layers. """
        for i in self._image:
            i.update()
    
    def copy(self):
        " Return a new Composite with the same values as the instance. "
        comp =  Composite(self._images, self._offset, self._pos, self._delay)
        comp.base = self
        return comp
    
    def revert(self):
        """ Copies values from the base to the instance. """
        newImages = base[:]
        newOffset = base.offset
        newPos = base.pos
        newDelay = base.delay
        
        newOffset = map(int, newOffset)
        newPos = map(int, newPos)
        newDelay = int(newDelay)
        if newDelay < 1:
            raise ValueError("base's delay < 1.")
        
        self._images = newImages
        self._offset = newOffset
        self._pos = newPos
        self._delay = newDelay
    
    def __getitem__(self,k):
        return self._image.__getitem__(k)
    
    def __setitem__(self,k,v):
        self._image.__setitem__(k,v)
    
    def __len__(self):
        return self._image.__len__()
    
    def __iter__(self):
        return self._image.__iter__()
    
    def append(self,img):
        " Add an image to the Composite as a new layer. "
        self._image.append(img)
    
    def extend(self,images):
        " Add multiple images each as a new layer. "
        self._image.extend(images)

class Animation(Composite):
    """ Image composed of sub-images (frames) displayed over time.
    
        An Animation is very similar to a Composite in that it is
        composed of other images. However, these other images are
        refered to as "frames" of the Animation rather than "layers,"
        and rather than drawing them all every time the Animation is
        drawn, they are each drawn one at a time over a peried of time.
        
        The actual animating of an Animation object is performed by
        the update() method. Its purpose is to advance an internal
        counter. When this counter reaches a certain value, the
        Animation advances to the next frame. The draw() method of the
        animation will, accordingly, only ever draw the current frame.
        
        Like Composite, the Animation object can be indexed,
        iterated, etc. with the frames as items.
        
        ~~~ Using Animations as Cookie Cutters ~~~
        
        In most situations one will want to have multiple
        occurences of an animation animating at a time, but not
        necessarily in unison. For this reason, the the copy() method
        of an Animation is especially useful.
        
        Animation's version of the copy() method has the added effect
        of giving the copy Animation a reference to the original. This
        works in conjunction with the revert() method, which will
        change an Animation's values back to those of the original.
        
        Because of this, it is usually a good idea to have a single
        "cookie-cutter" Animation object from which other Animations
        (the "cookies") are derived. An Animation used as a "cookie-
        cutter" is not normally drawn or advanced; that way, derived
        Animations will always revert to the most basic form possible.
        
        The internal reference can be accessed via the `base`
        attribute. If this attribute is None or not present, the
        Animation is an "orphan"--that is, an original. To turn a copy
        into an orphan (thus dropping its reference to the Animation
        from which it was derived) one calls the orphan() method. This
        method does nothing if the Animation is already an orphan.
    """
    
    tag = 'animation'
    
    def __init__(self,  images=None,
                        offset=(0,0),
                        pos=(0,0),
                        delay=1,
                        loop=None,
                        frame=0,
                        speed=1 ):
        """ Initialize an animation.
        
            images: A sequence of Images or Image-compatible objects to
                    use as frames.
            offset: Offset to apply to every draw.
            pos:    Default drawing position to use when None is given.
            delay:  Number of ticks during which this animation will be
                    shown when used as a frame of another animation.
            loop:   Indicate looping point. Pass None (default) to not
                    loop the animation. Must be integer >= 0 or None.
            frame:  Index of starting frame.
            speed:  Rate of counter advancement. Must be float between
                    0 and 1.
        """
        # Initialize Composite components (lol...sounds like an oxymoron)
        super(Animation,self).__init__(images, offset, pos, delay)
        
        # Store the animation length
        self._framec = framec = len(self._image)
        
        # Verify and store loop point
        if loop is not None:
            loop = int(loop)
            if loop < 0:
                raise ValueError("loop must be intger >= 0 or None; got %s"
                                 %loop)
        self._loop = loop
        
        # Verify and store the starting frame
        frame = int(frame)
        if not (0 <= frame < self._framec):
            raise ValueError("frame must be between 0 and len(images)-1")
        self._frame = frame
        
        # Verify and store speed
        speed = float(speed)
        if not (0 <= speed <= 1):
            raise ValueError("speed must be between 0 and 1.")
        self._speed = speed

        # Initialize the counter.
        self._counter = 0
    
    def __getitem__(self,f):
        self._image.__getitem__(f)
    
    def __setitem__(self,f,v):
        self._image.__setitem__(f,v)
        # Just a precaution...
        if len(self._image) != self._framec:
            self._counter = 0
        self._framec = len(self._image)
        # Another precaution...
        if self._frame >= self._framec:
            if self._loop is not None:
                self._frame = self._loop
            else:
                self._frame = self._framec-1
                self._speed = 0
    
    def to_json(self, resmgr=None, usenone=False):
        """ Creates a JSON representation of the Animation.
        
            resmgr:     A resource manager to use to cretae JSON reps
                        of frames in the animation. If None, the global
                        resource manager is used.
            
            usenone:    If true, frames that cannot be repped and do
                        not have filenames in the given resource
                        manager will be repped as None (JSON 'null') in
                        the representation of the Animation. If false
                        (default), encountering such a frame will
                        raise sbak.error.RepresentError.
            
            Returns a JSON representable dict.
            
            ~~~ JSON Format ~~~
            
            {
                "tag" :     "animation",
                "images " : [<image>, <image>, ...],
                "offset" :  [<int>, <int>],
                "pos" :     [<int>, <int>],
                "delay":    <int>,
                "loop":     <int or null>,
                "frame":    <unsigned int>,
                "speed":    <unsigned float>
            }
            
            ~~~ JSON Format Explanation ~~~
            
            "tag":      The tag for an Animation is always "animation".
            
            "images":   A list of filenames and/or JSON reps of images
                        in the animation.
                        
            "offset":   The drawing offset.
            
            "pos":      Default drawing position.
            
            "delay":    Amount of time to show the animation when used
                        as part of another animation.
                        
            "loop":     Position to loop animation to when it gets to
                        the end.
                        
            "frame":    Current frame of the animation. When loaded
                        from a file, it becomes the starting frame.
                        
            "speed":    Speed of the animation. This is the number of
                        timer steps taken per update. Must be between
                        0 and 1.
        """
        if resmgr is None:
            resmgr = resman.get_mgr()
        
        d = {
            "tag"   : self.tag,
            "images" : [resmgr.res_to_json(img, usenone, True)
                        for img in self],
            "pos"    : list(self.pos),
            "offset" : list(self.offset),
            "delay" : int(self.delay),
            "loop" : int(self.loop),
            "speed" : int(self.speed),
            "frame" : int(self.frame)
        }
        
        return d
    
    def append(self, image):
        """ Add a new frame to the end of the animation.
                
            You can add many items to the sequence at once using the
            extend() method.
        """
        self._image.append(image)
        self._framec = len(self._image)
    
    def extend(self, images):
        """ Add multiple frames to the end of the animation. """
        self._image.extend(images)
        self._framec = len(self._image)
    
    def update(self):
        """ Updates the internal counter and/or advances frame.
        
            This will increase the internal counter by the animation
            speed value. If the counter reaches the delay value of the
            current frame the frame will be advanced and the surface
            and offset of the animator will be changed to those of the
            new frame.
            
            Also, this will call the update() method of all frames.
        """
        if self._speed == 0 or self._framec == 0:
            return
        img = self._image[self._frame]
        self._counter += self._speed
        if self._counter >= img.delay:
            self._frame += 1
            self._counter = 0
            if self._frame >= self._framec:
                if self._loop is not None:
                    self._frame = self._loop
                else:
                    self._frame = self._framec-1
                    self._speed = 0
        for i in self._image:
            i.update()
    
    def draw(self, dest, offset=None, region=None):
        """ Draw the current frame of animation.
            
            dest:   Destination surface. Passed on to current frame.
            offset: Optional drawing offset. If None, the internal
                    default position is used. Otherwise, 
        """
        if not self._framec:
            return None
        if offset is None:
            offset = [a+b for a,b in zip(self._pos, self._offset)]
        else:
            offset = [a+b for a,b in zip(self._pos, offset)]
        self._image[self._frame].draw(dest,offset,region)
    
    def copy(self):
        """ Returns an Animation with the same values as self.
            
            This will create a new Animation object with the same
            images, offset, default position, loop point, current
            frame, and advancement speed as the calling instance's.
            Additionally, the copy's `base` attribute will be set
            to the calling instance.
        """
        anim = Animation(self._image, self._offset, self._pos, self._loop,
                         self._frame, self._speed)
        anim.base = self
        return anim

    def revert(self):
        """ Copies all values from the base into the instance.
        
            An AttributeError will occur if the `base` attribute is not
            present or is set to an object that does not have the
            necessary attributes. A ValueError will be raised if any
            values of the base are incorrect.
            
            Also, the counter will be reverted to 0.
        """
        # Get all required values from the base.
        newImages = self.base[:]
        newFramec = len(newImages)
        newOffset = self.base.offset[:]
        newPos = self.base.pos[:]
        newLoop = self.base.loop
        newFrame = self.base.frame
        newSpeed = self.base.speed
        
        # Verify new values.
        newOffset = map(int, newOffset)
        newPos = map(int, newPos)
        if newLoop is not None:
            newLoop = int(newLoop)
            if not (0 <= newLoop < newFramec):
                raise ValueError("base's loop is out of range.")
        newFrame = int(newFrame)
        if not (0 <= newFrame < newFramec):
            raise ValueError("base's frame is out of range.")
        newSpeed = float(newFrame)
        if not (0 <= newSpeed <= 1):
            raise ValueError("base's speed is not between 0 and 1.")
        
        # Apply new values.
        self._images = newImages
        self._offset = newOffset
        self._pos = newPos
        self._loop = newLoop
        self._frame = newFrame
        self._speed = newSpeed
    
    def set_frame(self,frame):
        """ Change current frame.
            Trying to set the frame to something outside the sequence
            will raise a ValueError.
        """
        frame = int(frame)
        if not (0 <= frame < self._framec):
            raise ValueError("frame value %i out of range."%frame)
        self._frame = frame
    
    frame = property(lambda self: self._frame, set_frame, doc=
        """ The current frame.
            Attempting to change to a value outside the sequence will
            raise a ValueError.
        """)

    def set_speed(self, speed):
        """ Change speed at which internal counter advances.
            Must pass a value between 0 and 1.
        """
        speed = float(speed)
        if not (0 <= speed <= 1):
            raise ValueError("Speed must be between 0 and 1.")
        self._speed = speed
    
    speed = property(lambda self: self._speed, set_speed, doc=
        """ Speed at which internal counter advances.
            Must be a value between 0 and 1.
        """)
    
    def set_loop(self,loop):
        if loop is not None:
            loop = int(loop)
            if not (0 <= loop < self._framec):
                raise ValueError("Loop point %i out of range."%loop)
        self._loop = loop
    loop = property(lambda self: self._loop, set_loop, doc=
        " Loop point. Set to None to not loop. ")


# = Functions =

def from_tilesheet(source, region, pos=(0,0), offset=(0,0), align=0, sep=(0,0),
                   adata=None, loop=None):
    """ Create an Animation from a tiled sprite sheet.
    
        A "tiled sprite sheet" is a surface that contains all the
        frames of an animation arranged in a grid as a single unit.
        This function can be used to break up such a surface into
        subsurfaces and use each one as a frame of a new Animation
        object.
        
        source: The source surface. This must be an actual Surface
                object and not a filename.
        region: The region of the surface containing the first frame.
                All subsequent frames will be deduced from this region
                and the other arguments. May be a x,y,w,h sequence or
                a Rect.
        pos:    Default drawing position.
        offset: Drawing offset (applied to all frames at draw time).
        align:  The alignment of tiles. 0=horizontal, 1=vertical.
                Default horizontal.
        sep:    The seperation (empty space) between each tile in the
                grid. Pass an x,y pair of ints.
        adata:  A sequence of animation data for each frame. Each item
                in the sequence must be another sequence of the form
                (int, (int,int)) where the first int is the delay for
                the corresponding frame and the other two are the
                offset. Alternately you may give None as one of the
                items to indicate to skip over the frame at that place
                in source.
        loop:   Loop point for the animation.
        
        The number of frames in the animation is deduced by the number
        of items in adata. If adata is not passed the number of frames
        in the animation is deduced from the size of the given region,
        seperation, alignment, and the size of the surface, and every
        frame will have delay 1 and offset (0,0).
        
        Returns an Animation object.
        
        TODO: Re-implement this function as a subclass of Animation.
    """
    # Determine all needed data from given arguments
    surfW,surfH = surfSize = source.get_size()
    regX,regY,regW,regH = Rect(region)
    sepX,sepY = sep
    maxCols,maxRows = (surfW-regX)//(regW+sepX), (surfH-regY)//(regH+sepY)
    if adata is not None:
        frames = len(adata)
    else:
        frames = maxCols*maxRows
        adata = ((1,(0,0)),)*frames
    
    # Loops for cutting up the surface
    fcounter = 0
    images = []
    # Loop for horizontal alignment
    if align==0:
        for row in xrange(maxRows):
            for col in xrange(maxCols):
                # Don't take more frames than are available
                if fcounter >= frames:
                    break
                # Determine region of the frame
                reg = Rect(col*(regW+sepX)+regX,
                           row*(regH+sepY)+regY, regW, regH)
                # Create and add the frame
                img = source.subsurface(reg)
                images.append((img,)+adata[fcounter])
                fcounter += 1
    # Loop for vertical alignment
    elif align==1:
        for col in xrange(maxCols):
            for row in xrange(maxRows):
                # Don't take more frames than are available
                if fcounter >= frames:
                    break
                # Determine region of the frame
                reg = Rect(col*(regW+sepX)+regX,
                           row*(regH+sepY)+regY, regW, regH)
                # Create and add the frame
                img = source.subsurface(reg)
                images.append((img,)+adata[fcounter])
                fcounter += 1
    # Raise error for unknown alignment values.
    else:
        raise ValueError("Unsupported alignment value: %s"%align)
    # Create the animation.
    anim = Animation(pos, offset, images, loop)
    return anim

def from_json(obj, resmgr=None):
    """ Creates an image object from a JSON object.
    
        obj:    A JSON representation of an image object.
        
        resmgr: A resource manager to use to load external
                requirements. If None is given, the global resource
                manager is used.
        
        Returns an Image, Composite, or Animation depending on the
        "tag" value of the mapping object given.
    """
    return {"image":Image,
            "composite":Composite,
            "animation":Animation}[obj["tag"]].from_json(obj, resmgr)

def load(src, resmgr=None, namehint=None, convert=True, alpha=True):
    """ Load an image from a file.
    
        src:        A file name or file-like object. This will be
                    loaded and parsed as a JSON object.
        
        resmgr:     A resource manager to load objects referenced in
                    the file. If None, the global resource manager
                    will be used.
                    
        (Other arguments are passed to load_surface if the file is a
        surface, otherwise they are unused and ignored.)
                    
        This can load images, composites, animations, and surfaces. If
        the file refers to a surface, then the surface will be loaded
        and wrapped inside an Image object.
        
        Returns Image, Composite, or Animation.
    """
    # Try to load the image as surface.
    try:
        surf = load_surface(src, resmgr=resmgr, namehint=namehint,
                            convert=convert, alpha=alpha)
    except pygame.error:
        pass
    else:
        return Image(surf)
    
    # If the src is a string, assume it is a filename and open it.
    close = False # (default value)
    try:
        if isinstance(src, basestring):
            close = True
            src = open(src)
        
        # Parse the file with JSON.
        obj = json.load(src)

    # Close the file if necessary
    finally:
        if close:
            src.close()
    
    # Turn the JSON into an image
    obj = from_json(obj, resmgr)
    
    return obj

def load_surface(src, resmgr=None, namehint=None, convert=True, alpha=True):
    """ Load a surface from a file.
    
        src:        A file name of a valid Pygame surface or a file-
                    like object containing image data in a recognized
                    file format.
        
        resmgr:     Unused.
        
        namehint:   Used to help determine the file type of image data.
                    If src is a file-like object rather than a string,
                    then Pygame will use this as the filename, and the
                    extension will be used to determine how to handle
                    the image data.
        
        convert:    If the file is a surface, this specifies whether or
                    not to convert to the current screen depth. Default
                    is True.
        
        alpha:      If the file is a surface, this specified whether or
                    not to use alpha transparency. If this is True,
                    convert is assumed to be True as well. Default is
                    True.
                    
        This is a wrapper around pygame.image.load that allows surfaces
        to be loaded using the resource management system, and also
        allows for surfaces to be converted to various formats.
    """
    # Try to get the namehint into either string or None
    if not isinstance(namehint,basestring):
        namehint = src
        if not isinstance(namehint,basestring):
            namehint = None
    
    # If a namehint was given, use it.
    if namehint is not None:
        surf = pygame.image.load(src, namehint)
        
    # If not, don't.
    else:
        surf = pygame.image.load(src)
    
    # Convert to the given format.
    if alpha:
        surf = surf.convert_alpha()
    elif convert:
        surf = surf.convert()
    
    return surf
    